package com.h2cloud.JUCTest.reentrantReadWriteLock;

/**
 * @Package: com.h2cloud.JUCTest.reentrantReadWriteLock
 * @ClassName: cacheTest
 * @Author: h2cloud
 * @CreateTime: 2022/6/5 2:37 下午
 * @Description:
 */

import lombok.Data;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * ReentrantReadWriteLock读写锁测试
 */
public class cacheTest {
	public static void main(String[] args) {
		GeneriCacheDao<Object> generiCacheDao = new GeneriCacheDao<>();

		Object[] objects = new Object[2];
		System.out.println(generiCacheDao.queryOne(Object.class, "Test", objects));
		System.out.println(generiCacheDao.queryOne(Object.class, "Test", objects));
		System.out.println(generiCacheDao.queryOne(Object.class, "Test", objects));
		System.out.println(generiCacheDao.queryOne(Object.class, "Test", objects));

		System.out.println(generiCacheDao.map);
		generiCacheDao.update("Test", objects);
		System.out.println(generiCacheDao.map);
	}

}

/**
 * 适合读多写少
 * 没有考虑缓存容量
 * 没有考虑缓存过期
 * 只适合单机
 * 并发性还是低，目前只会用一把锁（不同表用不同的锁）
 * 更新方法简单粗暴，清空了所有 key（考虑按类型分区或重新设计 key）
 * @param <T>
 */
class GeneriCacheDao<T> {
	HashMap<SqlPair, T> map = new HashMap<>();
	ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	GenericDao genericDao = new GenericDao();


	public int update(String sql, Object... params) {
		lock.writeLock().lock();
		try {
			// 先查询数据库再更新缓存。但这里加了锁，谁先谁后都没关系
			int update = genericDao.update(sql, params);
			map.clear();
			return update;
		} finally {
			lock.writeLock().unlock();
		}
	}

	public T queryOne(Class<T> beanClass, String sql, Object... params) {
		SqlPair key = new SqlPair(sql, params);
		// 加读锁，防止其它线程对缓存更改
		lock.readLock().lock();
		try {
			T value = map.get(key);
			if (value != null) {
				return value;
			}
		} finally {
			lock.readLock().unlock();
		}

		// 加写锁，防止其它线程对缓存读取和更改
		lock.writeLock().lock();
		// get 方法上面部分是可能多个线程进来的，可能已经向缓存填充了数据
		// 为防止重复查询数据库，再次验证
		try {
			T value = map.get(key);
			if (value == null) {
				value = (T) genericDao.queryOne(beanClass, sql, params);
				map.put(key, value);
			}
			return value;
		} finally {
			lock.writeLock().unlock();
		}
	}


	@Data
	class SqlPair {
		private String sql;

		private Object[] params;

		public SqlPair(String sql, Object[] params) {
			this.sql = sql;
			this.params = params;
		}
	}
}

class GenericDao<T> {
	public int update(String sql, Object... params) {
		return 1;
	}

	public T queryOne(Class<T> beanClass, String sql, Object... params) {
		System.out.println("查询数据库中");

		return (T) new Object();
	}
}
